mod db;
mod files;
pub mod record;

use crate::datasource::files::FilesDataSource;
use crate::task::Task;
use db::mysql::MySQL;
use db::oracle::Oracle;
use db::postgres::Postgres;
use record::Record;
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::marker::Copy;

/// support DataSourceTypes
///
#[derive(Deserialize, PartialEq, Debug, Copy, Clone)]
pub enum DataSourceTypes {
    mysql,         //mysql database
    postgres,      //postgresql database
    oracle,        //oracle database
    elasticsearch, //elasticsearch
    opensearch,    //opensearch
    files,         //files in path to index
}

/// DataSource Configurations
///
#[derive(Deserialize, PartialEq, Debug)]
pub struct DataSource {
    pub source: DataSourceTypes,
    #[serde(default)]
    pub url: String,
    #[serde(default)]
    pub path: String,
    #[serde(default)]
    pub username: String, // username for oracle database
    #[serde(default)]
    pub password: String, // password for oracle database
}

/// Data load interface
///
pub trait IDataSource {
    fn new(ds: &DataSource) -> Self
    where
        Self: Sized;

    /// initialize datasource
    fn init(&self, tasks: &HashMap<&String, &Task>) -> Result<(), String>;

    /// test datasource
    fn ping(&self) -> Result<bool, String>;

    /// iterate all records to indexea
    ///
    /// # Arguments
    ///
    /// * `task` - task instance
    /// * `limit` - records count
    /// * `offset` - records offset
    ///
    fn records(&self, task: &Task, limit: u32, offset: u32) -> Result<Vec<Value>, String>;

    /// list todo task in to be pushed to indexea
    ///
    /// # Arguments
    ///
    /// * `name` - task name
    /// * `task` - task detail
    /// * `count` - batch size
    ///
    fn tasks(&self, name: &String, task: &Task, count: i32) -> Result<Vec<Record>, String>;

    /// mark todo task status to `done`
    ///
    /// # Arguments
    ///
    /// * `tasks` - task list
    ///
    fn finish(&self, records: &Vec<Record>) -> Result<(), String>;

    /// cleanup datasource
    fn clean(&self, tasks: &HashMap<&String, &Task>) -> Result<(), String>;
}

impl DataSource {
    /// build an IDataSource instance
    pub fn inst(&self) -> Result<Box<dyn IDataSource>, String> {
        match self.source {
            DataSourceTypes::mysql => Ok(Box::new(MySQL::new(self))),
            DataSourceTypes::postgres => Ok(Box::new(Postgres::new(self))),
            DataSourceTypes::oracle => Ok(Box::new(Oracle::new(self))),
            DataSourceTypes::files => Ok(Box::new(FilesDataSource::new(self))),
            _ => Err(format!("datasource {:?} unsupported.", self.source)),
        }
    }
}

impl Clone for DataSource {
    fn clone(&self) -> DataSource {
        DataSource {
            source: self.source.clone(),
            url: self.url.clone(),
            username: self.username.clone(),
            password: self.password.clone(),
            path: self.path.clone(),
        }
    }
}
